/**
 * Python code error checker using TPyParser (TigerPython parser).
 * TODO: This is just a very very early proof of concept, still needs to be
 * refined before it's ready.
 */
'use strict';

/**
 * @returns An object with the error checker wrapper.
 */
var ErrorChecker = function() {
    var sendErrorsCallbacks = [];
    var checkerWorker = null;
    var currentErrors = 0;
    var nextCodeToParse = '';
    // TODO: This should probably be a list of unique ids that we remove as we
    // receive the results 
    var parsingInProgress = false;

    // TPyParser configuration data
    var parserConfig = {
        pythonVersion: 3,
        rejectDeadCode: true,
        strictCode: true,
    };

    /**
     * Enables the code checker.
     * If the web worker hasn't been configure yet it does it at this point.
     * 
     * @param {function} sendErrorsCallback_ - Function to call back when the
     *      parser has processed the code errors.
     */
    function enable(sendErrorsCallback_) {
        if (!checkerWorker) {
            var isIE = /MSIE|Trident/.test(window.navigator.userAgent);
            if (!window.Worker || isIE) {
                throw new Error('Code Checker cannot be used in this browser, please try with Chrome.');
            }
            checkerWorker = new Worker('js/error-checker-worker.js');

            // Send the config data to the webworker
            checkerWorker.postMessage({
                type: 'checker_config',
                data: parserConfig,
            });

            // Event listener to receive the parsed errors
            checkerWorker.onmessage = function(evt) {
                if (evt.data.type === 'checker_errors') {
                    processReceivedParsedErrors(evt.data.data);
                }
            };
        }
        registerCallback(sendErrorsCallback_);
    }

    function disable(sendErrorsCallback_) {
        if (sendErrorsCallback_) {
            removeCallback(sendErrorsCallback_);
        } else {
            sendErrorsCallbacks = [];
        }
        nextCodeToParse = '';
    }

    function registerCallback(sendErrorsCallback_) {
        if (typeof sendErrorsCallback_ !== 'function') {
            throw new Error('Enabling the Checker requires a callback function.');
        }
        if (sendErrorsCallbacks.indexOf(sendErrorsCallback_) === -1) {
            sendErrorsCallbacks.push(sendErrorsCallback_)
        } else {
            console.log('Checker: Callback already registered.')
        }
    }

    function removeCallback(sendErrorsCallback_) {
        var callbackIndex = sendErrorsCallbacks.indexOf(sendErrorsCallback_);
        if (callbackIndex !== -1) {
            sendErrorsCallbacks.splice(callbackIndex, 1);
        }
    }

    /**
     * Sends code to the web worker parser.
     *
     * @param {string} code - Code to send to the web worker to parse
     */
    function sendCodeToParse(code) {
        if (checkerWorker) {
            parsingInProgress = true;
            // var date = new Date(); console.log('requesting parse', date.getSeconds(), date.getMilliseconds());
            checkerWorker.postMessage({
                type: 'checker_code',
                data: code,
            });
        }
    }

    /**
     * Takes the errors object generated by the parser, converts it, sends it
     * to the registered callback, and if there is a queued code string to
     * parse it requests it to the worker.
     *
     * @param {Object} parsedErrors - Error object received directly from the
     *      web worker parser.
     */
    function processReceivedParsedErrors(parsedErrors) {
        parsingInProgress = false;
        // Keep track of this value in this attribute
        currentErrors = parsedErrors.length;
        // Convert to a custom format
        var errorObjToSend = parsedErrors.map(function(error) {
            return {
                line_start: error.Uq,
                column_start: error.Wq,
                line_end: error.Uq,
                column_end: null,
                message: error.Vq,
            };
        });
        // We have a "queue" of code to process, but we only parse the latest
        // code sent and ignore all previous requests
        if (nextCodeToParse && sendErrorsCallbacks.length) {
            sendCodeToParse(nextCodeToParse);
        }
        nextCodeToParse = '';
        sendErrorsCallbacks.forEach(function(callback) {
            callback(errorObjToSend);
        });
    }

    /**
     * Request a code parse to the web worker.
     * If the worker is already parsing code, then we queue the last received
     * code to be parsed immediately after the current parsing is done.
     * For "next parsing" we only keep the last version of the code sent here.
     * 
     * @param {string} code - Code to parse for errors.
     */
    var sendDebounce = null;
    function parseCode(code) {
        if (sendErrorsCallbacks.length) {
            if (parsingInProgress) {
                nextCodeToParse = code;
            } else {
                 // Send code to the web worker with a small debounce
                 clearTimeout(sendDebounce);
                 sendDebounce = window.setTimeout(function() {
                     sendCodeToParse(code);
                 }, 2000);
            }
        }
    }

    return {
        'enable': enable,
        'disable': disable,
        'getCurrentErrors': function() {
            return currentErrors;
        },
        'parseCode': parseCode,
    };
}

if (typeof module !== 'undefined' && module.exports) {
    global.ErrorChecker = ErrorChecker;
} else {
    window.ErrorChecker = ErrorChecker;
}
